using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using HestiaBackup.ExternalTools.CS.MSDN;
using HestiaBackup.Library.ConfigurationManager;
using HestiaBackup.Library.Events;
using HestiaBackup.Library.Workers;

namespace HestiaBackup.Library.Connectors
{
    public class FtpConnector
    {
        private FtpSetting _setting;

        public FtpSetting Setting
        {
            get { return _setting; }
        }

        public FtpConnector(FtpSetting setting)
        {
            _setting = setting;
        }

        public void Process()
        {
            GetFiles();
        }

        private void GetFiles()
        {
            allFiles = new Dictionary<string, FileStruct>(StringComparer.InvariantCultureIgnoreCase);
            allFolders = new Dictionary<string, FileStruct>(StringComparer.InvariantCultureIgnoreCase);
            if (GetFiles(Setting.RemoteFolder))
            {
                ProcessFiles();
                CheckFileDeletion();
            }
            else
            {
                FtpChildWorker.RaiseError("Error during file list retrieving, backup operation canceled", Setting);
            }
        }

        private void CheckFileDeletion()
        {
            CheckFileDeletion(Setting.LocalFolder, Setting.RemoteFolder);
        }

        private void CheckFileDeletion(string localPath, string remotePath)
        {
            string[] files = Directory.GetFiles(localPath);
            for (int i = 0; i < files.Length; i++)
            {
                string remoteLookingFileName = remotePath + "/" + Path.GetFileName(files[i]);
                if (!allFiles.ContainsKey(remoteLookingFileName))
                {
                    if (Setting.KeepDeletedFiles)
                    {
                        MoveDeletedFile(files[i]);
                    }
                    else
                    {
                        File.Delete(files[i]);
                    }
                }
            }

            string[] directories = Directory.GetDirectories(localPath);
            for (int i = 0; i < directories.Length; i++)
            {
                string remoteLookingDirectoryName = remotePath + "/" + directories[i].Substring(directories[i].LastIndexOf('\\') + 1);
                if (!allFolders.ContainsKey(remoteLookingDirectoryName))
                {
                    if (Setting.KeepDeletedFiles)
                    {
                        MoveDeletedDirectory(directories[i]);
                    }
                    else
                    {
                        Directory.Delete(directories[i], true);
                    }
                }
                else
                {
                    CheckFileDeletion(directories[i], remotePath + "/" + directories[i].Substring(directories[i].LastIndexOf('\\') + 1));
                }
            }
        }

        private void MoveDeletedDirectory(string directoryName)
        {
            string[] directories = directoryName.Replace(Setting.LocalFolder, "").Split('\\');
            string localDeletedDirectoryName = Setting.LocalDeletedFolder;
            for (int i = 0; i < directories.Length; i++)
            {
                if (!string.IsNullOrEmpty(directories[i]))
                {
                    if (i < directories.Length - 1)
                    {
                        localDeletedDirectoryName = Path.Combine(localDeletedDirectoryName, directories[i]);
                        if (!Directory.Exists(localDeletedDirectoryName))
                        {
                            Directory.CreateDirectory(localDeletedDirectoryName);
                        }
                    }
                }
            }
            localDeletedDirectoryName = Path.Combine(localDeletedDirectoryName, directories[directories.Length - 1]);
            localDeletedDirectoryName += "deleted." + DateTime.Now.ToString("yyyy.MM.dd.HH.mm");
            if (Directory.Exists(localDeletedDirectoryName))
            {
                Directory.Delete(localDeletedDirectoryName);
            }
            Directory.Move(directoryName, localDeletedDirectoryName);
        }

        private void MoveDeletedFile(string fileName)
        {
            string[] directories = fileName.Replace(Setting.LocalFolder, "").Split('\\');
            string localDeletedFileName = Setting.LocalDeletedFolder;
            for (int i = 0; i < directories.Length; i++)
            {
                if (!string.IsNullOrEmpty(directories[i]))
                {
                    if (i < directories.Length - 1)
                    {
                        localDeletedFileName = Path.Combine(localDeletedFileName, directories[i]);
                        if (!Directory.Exists(localDeletedFileName))
                        {
                            Directory.CreateDirectory(localDeletedFileName);
                        }
                    }
                }
            }
            localDeletedFileName = Path.Combine(localDeletedFileName, directories[directories.Length - 1]);
            localDeletedFileName += "deleted." + DateTime.Now.ToString("yyyy.MM.dd.HH.mm");
            if (File.Exists(localDeletedFileName))
            {
                File.Delete(localDeletedFileName);
            }
            File.Move(fileName, localDeletedFileName);
        }

        private void ProcessFiles()
        {
            foreach (string key in allFiles.Keys)
            {
                CheckFile(allFiles[key]);
            }
        }

        private bool CheckFile(FileStruct file)
        {
            string localFileName = Setting.LocalFolder;
            string[] folders = file.Name.Replace(Setting.RemoteFolder, "").Split('/');
            for (int i = 0; i < folders.Length; i++)
            {
                if (!string.IsNullOrEmpty(folders[i]))
                {
                    if (i < folders.Length - 1)
                    {
                        localFileName = Path.Combine(localFileName, folders[i]);
                        if (!Directory.Exists(localFileName))
                        {
                            Directory.CreateDirectory(localFileName);
                        }
                    }
                }
            }
            localFileName = Path.Combine(localFileName, folders[folders.Length - 1]);

            if (!File.Exists(localFileName))
            {
                Download(localFileName, file.Name, file.CreateTime);
                return false;
            }

            FileInfo info = new FileInfo(localFileName);
            if (file.CreateTime > info.CreationTime)
            {
                Download(localFileName, file.Name, file.CreateTime);
                return false;
            }
            if (OnFileAlreadyUpToDate != null)
            {
                FileDownloadedEventArgs e = new FileDownloadedEventArgs();
                e.fileInfo = info;
                OnFileAlreadyUpToDate(this, e);
            }
            return true;
        }

        private void Download(string localFileName, string remoteFileName, DateTime remoteDate)
        {
            FtpWebRequest request = (FtpWebRequest) FtpWebRequest.Create("ftp://" + Setting.Server + remoteFileName);
            request.UseBinary = true;
            request.UsePassive = true;
            request.Credentials = new NetworkCredential(Setting.User, Setting.Password);
            request.Method = WebRequestMethods.Ftp.DownloadFile;
            try
            {
                FtpWebResponse response = (FtpWebResponse) request.GetResponse();
                Stream ftpStream = response.GetResponseStream();
                FileStream outputStream = new FileStream(localFileName, FileMode.Create);
                long cl = response.ContentLength;
                int bufferSize = 2048;
                int readCount;
                byte[] buffer = new byte[bufferSize];

                readCount = ftpStream.Read(buffer, 0, bufferSize);
                while (readCount > 0)
                {
                    outputStream.Write(buffer, 0, readCount);
                    readCount = ftpStream.Read(buffer, 0, bufferSize);
                }

                ftpStream.Close();
                outputStream.Close();
                response.Close();
                FileInfo info = new FileInfo(localFileName);
                info.CreationTime = remoteDate;
                if (OnFileDownloaded != null)
                {
                    FileDownloadedEventArgs e = new FileDownloadedEventArgs();
                    e.fileInfo = info;
                    OnFileDownloaded(this, e);
                }
            }
            catch (WebException ex)
            {
                FtpChildWorker.RaiseError(ex.Message, Setting);
            }
        }

        private Dictionary<string, FileStruct> allFiles;
        private Dictionary<string, FileStruct> allFolders;

        private bool GetFiles(string remoteSubDir)
        {
            FtpWebRequest request = (FtpWebRequest) FtpWebRequest.Create("ftp://" + Setting.Server + remoteSubDir);
            request.UseBinary = true;
            request.UsePassive = true;
            request.Credentials = new NetworkCredential(Setting.User, Setting.Password);
            request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
            string line;
            try
            {
                WebResponse response = request.GetResponse();
                StringBuilder result = new StringBuilder();
                StreamReader reader = new StreamReader(response.GetResponseStream());
                line = reader.ReadToEnd();
                reader.Close();
                response.Close();
            }
            catch (WebException ex)
            {
                FtpChildWorker.RaiseError(ex.Message, Setting);
                return false;
            }
            ParseListDirectory parseList = new ParseListDirectory();
            FileStruct[] files = parseList.GetList(line);
            for (int i = 0; i < files.Length; i++)
            {
                if (files[i].IsDirectory)
                {
                    files[i].Name = remoteSubDir + "/" + files[i].Name;
                    allFolders.Add(files[i].Name, files[i]);
                    GetFiles(files[i].Name);
                }
                else
                {
                    files[i].Name = remoteSubDir + "/" + files[i].Name;
                    allFiles.Add(files[i].Name, files[i]);
                }
            }
            return true;
        }


        public event EventHandler<FileDownloadedEventArgs> OnFileDownloaded;
        public event EventHandler<FileDownloadedEventArgs> OnFileAlreadyUpToDate;
    }
}